﻿#region License Information

/* •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————•
   | Copyright Aptillon, Inc.                                                                                        |
   | This file is part of the CKS:API project - cksapi.codeplex.com                                                       |
   | Usage, modification and distribution subject to the license terms available here: http://cksapi.codeplex.com/license |
   |                                                                                                                      |
   | Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,             |
   | either express or implied.                                                                                           |
   |                                                                                                                      |
   | THIS MESSAGE MAY NOT BE LEGALLY REMOVED                                                                              |
   •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————• */

#endregion

using System;
using System.Web.UI;
using System.Web;
using CKS.API.Logging;
using System.Web.UI.WebControls;
using CKS.API.Utilities;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using CKS.API.Contracts;
using System.Reflection;
using System.Resources;
using System.Diagnostics;

namespace CKS.API.Exceptions
{
    /// <summary>
    /// Base exception class for all framework exceptions.  
    /// </summary>
    /// <remarks>
    /// This base class ensures that a standard user friendly error message is passed back to the caller along with a a unique identifier
    /// for a specific exception instance while the full exception details are logged with the same unique ID.
    /// 
    /// Modification History: NONE
    /// </remarks>
    [Serializable]
    public abstract class CKSException : Exception
    {

        #region Constants

        const int defaultDialogHeight = 600;

        const int defaultDialogWidth = 800;

        #endregion

        #region Fields

        private Guid _CorrelationId;

        private Exception inner = null;

        private CKSStatusBarColor statusBarColor = CKSStatusBarColor.blue;

        private string userDisplayText = Resources.FriendlyUserErrorText;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor - writes actual error message to the log 
        /// </summary>
        /// <remarks>
        /// <para>This constructor is responsible for passing the friendly error message to the caller (in the call to base() as well as recording the 
        /// full error details in the log (in the constructor body).   </para>
        /// </remarks>
        internal CKSException(
            string logMessage,
            Exception innerException,
            CKSLoggingCategory category,
            Guid correlationId)
            : base(string.Format(Resources.FriendlyUserErrorText, correlationId), innerException)
        {
            LogMessage = logMessage;
            Category = category;
            inner = innerException;
        }

        #endregion

        #region Properties

        public CKSLoggingCategory Category { get; set; }

        public Guid CorrelationId
        {
            get
            {
                if (Check.ThatIsNull(_CorrelationId) || Check.That(_CorrelationId.ValidateIsEmptyGuid()))
                {
                    _CorrelationId = Guid.NewGuid();
                }
                return _CorrelationId;
            }
            set
            {
                _CorrelationId = value;
            }
        }

        public string LogMessage { get; set; }

        public Page ThrowingPage { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds the <![CDATA[<script...>]]> tag to the header of the page specified by the <paramref name="throwingControl"/> parameter to ensure that the proper JavaScript
        /// is added to the page to show the user the friendly error message as either a dialog box or an entry on the Status Bar.
        /// </summary>
        /// <param name="throwingControl">The <see cref="System.Web.UI.Page"/> to which the JavaScript is added.</param>
        /// <exception cref="" />
        public void AddErrorDialogScript(Control targetControl, string correlationId)
        {
            //TODO: Does this need to be public?
            targetControl.Controls.Add(GetCommonErrorScriptLibrary());
            targetControl.Controls.Add(new LiteralControl(AddCallErrorScript("showDialog", correlationId)));

        }

        public void AddErrorStatusBarScript(Control targetControl, string correlationId)
        {
            //TODO: Does this need to be public?
            targetControl.Controls.Add(GetCommonErrorScriptLibrary());
            targetControl.Controls.Add(new LiteralControl(AddCallErrorScript("emitStatus", correlationId)));

        }

        #endregion

        #region Internal Methods

        internal void Process(CKSExceptionDetails details, CKSLogger logger)
        {
            string logMessage = string.Format("Error ID: {0}.  Message: {1}",
                CorrelationId,
                LogMessage);
            while (null != inner)
            {
                logMessage += string.Format("_____InnerException: {0}", inner.Message);
                inner = inner.InnerException;
            }
            logMessage += string.Format("_____StackTrace: {0}", new StackTrace());
            logger.Write(logMessage, Category, details.Severity);
            Control targetControl = GetTargetControl(details.ThrowingControl);
            if (Check.ThatAllAreNotNull(HttpContext.Current, targetControl))
            {
                if (
                    (!string.IsNullOrEmpty(details.UserText))
                    && (!details.UserText.Equals(userDisplayText, StringComparison.InvariantCultureIgnoreCase))
                    )
                {
                    userDisplayText = details.UserText;
                }

                switch (details.Display)
                {
                    case ErrorDisplayType.Dialog:
                        AddErrorDialogScript(targetControl, CorrelationId.ToString());
                        break;
                    case ErrorDisplayType.StatusBar:
                        statusBarColor = details.StatusBarColor;
                        AddErrorStatusBarScript(targetControl, CorrelationId.ToString());
                        break;
                    case ErrorDisplayType.None:
                        break;

                }

            }

            if (details.Severity == LoggingSeverity.CriticalError)
            {
                logger.NotifyAdministrator(details.Exception.CorrelationId.ToString(), logMessage);
            }
        }

        #endregion

        #region Private Methods

        private string AddCallErrorScript(string functionName, string correlationId)
        {

            return AddCallErrorScript(functionName, defaultDialogHeight, defaultDialogWidth, correlationId);
        }

        private string AddCallErrorScript(string functionName, int dialogHeight, int dialogWidth, string correlationId)
        {
            return string.Format(@"<script type='text/ecmascript' language='ecmascript'>var statusId;{0}ExecuteOrDelayUntilScriptLoaded({1}, 'sp.js');</script>",
                                            AddErrorVariables(dialogWidth, dialogHeight, correlationId),
                                            functionName);

        }

        private string AddErrorVariables(int dialogWidth, int dialogHeight, string correlationId)
        {
            return string.Format(@"
                              var serverRelativeUrl='{0}/';
                              var errorPageName='{1}';
                              var correlationId='{2}';
                              var dialogWidth={3};
                              var dialogHeight={4};
                              var errorDialogTitle='{5}';
                              var friendlyErrorText='{6}';
                              var statusBarColor='{7}';",
                                                    SPContext.Current.Site.RootWeb.ServerRelativeUrl.TrimEnd('/'),
                                                    Constants.ErrorPageName,
                                                    correlationId,
                                                    dialogWidth,
                                                    dialogHeight,
                                                    Resources.ErrorPageTitle,
                                                    userDisplayText,
                                                    Enum.GetName(typeof(CKSStatusBarColor), statusBarColor));

        }

        private Control GetCommonErrorScriptLibrary()
        {
            return new LiteralControl(string.Format("<script type='text/ecmascript' language='ecmascript' src='{0}/SiteAssets/CKS/{1}'></script>", SPContext.Current.Site.RootWeb.ServerRelativeUrl.TrimEnd('/'), Constants.JSLibraryName));
        }

        private static Control GetTargetControl(Control throwingControl)
        {
            try
            {
                if (CKSApiUtilities.IsInSandbox)
                {
                    if (throwingControl is WebPart)
                    {
                        return throwingControl.Page.Controls[0].Controls[0].Controls[0] as WebControl;
                    }
                    if (throwingControl is UserControl)
                    {
                        return throwingControl.Page;
                    }
                }
                else
                {
                    return throwingControl.Page;
                }
            }
            catch
            {
                return null;
            }
            return null;

        }

        #endregion

    }
}
